import * as THREE from "three";

// 引入轨道控制器控制器
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
// 引入动画库
import gsap from "gsap";
// 引入数据图形用户界面库
import * as dat from "dat.gui";
// 引入rgbeLoad
// DataTextureLoader
// 用于加载二进制文件格式的(rgbe, hdr, ...)的抽象类。 内部使用FileLoader来加载文件， 和创建一个新的 DataTexture.
import { RGBELoader } from "three/examples/jsm/loaders/RGBELoader.js"
// 引入物理引擎
import * as CANNON from 'cannon-es';
// 导入顶点着色器与片元着色器
import FlyLightVertexShader from "../shader/flyLight/FlyLightVertexShader.glsl";
import FlyLightFragmentShader from "../shader/flyLight/FlyLightFragmentShader.glsl";

// 用于载入glTF 2.0资源的加载器。
// glTF（gl传输格式）是一种开放格式的规范 （open format specification）， 用于更高效地传输、加载3D内容。
// 该类文件以JSON（.gltf）格式或二进制（.glb）格式提供， 外部文件存储贴图（.jpg、.png）和额外的二进制数据（.bin）。
// 一个glTF组件可传输一个或多个场景， 包括网格、材质、贴图、蒙皮、骨架、变形目标、动画、灯光以及摄像机。
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js"

//three.js 基本内容

// 1.创建场景  场景能够让你在什么地方、摆放什么东西来交给three.js来渲染，这是你放置物体、灯光和摄像机的地方。
const scene = new THREE.Scene();

// 2.创建相机 

//  透视相机（PerspectiveCamera）这一摄像机使用perspective projection（透视投影）来进行投影。
// 这一投影模式被用来模拟人眼所看到的景象，它是3D场景的渲染中使用得最普遍的投影模式。
// PerspectiveCamera( fov : Number, aspect : Number, near : Number, far : Number )
// fov — 摄像机视锥体垂直视野角度   // aspect — 摄像机视锥体长宽比  // near — 摄像机视锥体近端面  // far — 摄像机视锥体远端面
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 300);

//2.1设置相机的位置 position表示对象局部位置的Vector3。默认值为(0, 0, 0)。

// Vector3( x : Float, y : Float, z : Float )   该类表示的是一个三维向量（3D vector）。'
//  一个三维向量表示的是一个有顺序的、三个为一组的数字组合（标记为x、y和z）， 可被用来表示很多事物:
//  一个位于三维空间中的点。
// 一个在三维空间中的方向与长度的定义。在three.js中，长度总是从(0, 0, 0)到(x, y, z)的 Euclidean distance（欧几里德距离，即直线距离）， 
// 方向也是从(0, 0, 0)到(x, y, z)的方向。
// .set ( x : Float, y : Float, z : Float ) : this
// 设置该向量的x、y 和 z 分量。

camera.position.set(0, 1, 5);
scene.add(camera); //添加相机到场景中

// 创建rgbe纹理加载器对象
const rgbeLoader = new RGBELoader();
rgbeLoader.loadAsync("./texture/2k.hdr").then((texture) => {
    // console.log(texture)
    // 图像将如何应用到物体（对象）上。
    texture.mapping = THREE.EquirectangularReflectionMapping;
    // 设置场景背景 物理材质的环境贴图
    scene.background = texture;
    scene.environment = texture;
});

const shaderMaterial = new THREE.ShaderMaterial({
    // 顶点着色器的GLSL代码。这是shader程序的实际代码。 
    // gl_Position 顶点变换要更新位置信息
    vertexShader: FlyLightVertexShader,
    // 片元着色器的GLSL代码。这是shader程序的实际代码。
    fragmentShader: FlyLightFragmentShader,
    side: THREE.DoubleSide,
    // .uniforms : Object
    // { "uniform1": { value: 1.0 }, "uniform2": { value: 2 } }
    // 指定要传递给shader代码的uniforms；键为uniform的名称，值(value)是如下形式：
    // { value: 1.0 }这里 value 是uniform的值。名称必须匹配 uniform 的name，和GLSL代码中的定义一样。 
    // 注意，uniforms逐帧被刷新，所以更新uniform值将立即更新GLSL代码中的相应值。
    uniforms: {
        uTime: {
            value: 0
        }
    }
});

// 加载gltf
const gltfLoader = new GLTFLoader();
gltfLoader.load("./model/flyLight.glb", (glft) => {
    // console.log(glft);
    let lightBox = glft.scene.children[1];
    lightBox.material = shaderMaterial;

    for (let i = 0; i < 150; i++) {
        let flyLight = glft.scene.clone(true);
        let x = (Math.random() - 0.5) * 300;
        let z = (Math.random() - 0.5) * 300;
        let y = Math.random() * 100 + 25;
        flyLight.position.set(x, y, z);
        // 设置动画
        gsap.to(flyLight.rotation, {
            y: 2 * Math.PI,
            duration: 10 + Math.random() * 10,
            repeat: -1
        })
        gsap.to(flyLight.position, {
            x: "+=" + Math.random() * 5,
            y: "+=" + Math.random() * 20,
            duration: 5 + Math.random() * 10,
            repeat: -1,
            yoyo:true
        })
        scene.add(flyLight);
    }
})

// 4.渲染
// 初始化渲染器
// WebGLRenderer WebGL Render 用WebGL渲染出你精心制作的场景。
const renderer = new THREE.WebGLRenderer();
// 设置渲染的尺寸大小
renderer.setSize(window.innerWidth, window.innerHeight);
// .domElement : DOMElement   一个canvas，渲染器在其上绘制输出。
// 渲染器的构造函数会自动创建(如果没有传入canvas参数);你需要做的仅仅是像下面这样将它加页面里去:
document.body.appendChild(renderer.domElement); //将绘制canvas添加到页面里
// 开启场景中的阴影贴图
// renderer.shadowMap.enabled = true;
// renderer.physicallyCorrectLights = true; // 新版本不用设置 物理正确光源 decay也会生效。 老版本开阴影要设置
// 设置渲染器的rgbe设置
// .outputColorSpace : string 定义渲染器的输出编码。默认为THREE.SRGBColorSpace
renderer.outputEncoding = THREE.sRGBEncoding; //文档没有
// .toneMapping : Constant 色调映射
// 默认是NoToneMapping。查看Renderer constants以获取其它备选项这些常量定义了WebGLRenderer中toneMapping的属性。 
// 这个属性用于在普通计算机显示器或者移动设备屏幕等低动态范围介质上，模拟、逼近高动态范围（HDR）效果。
renderer.toneMapping = THREE.ACESFilmicToneMapping;
// .toneMappingExposure : Number
// 色调映射的曝光级别。默认是1
renderer.toneMappingExposure = 0.2;


//创建轨道控制器
// OrbitControls( object : Camera, domElement : HTMLDOMElement )
// object: （必须）将要被控制的相机。该相机不允许是其他任何对象的子级，除非该对象是场景自身。
// domElement: 用于事件监听的HTML元素。
const controls = new OrbitControls(camera, renderer.domElement);
// 将其设置为true以启用阻尼（惯性），这将给控制器带来重量感。默认值为false。
// 请注意，如果该值被启用，你将必须在你的动画循环里调用.update()。
controls.enableDamping = true;
// 设置控制器旋转与 垂直旋转的角度的上下限
controls.autoRotate = true;
controls.autoRotateSpeed = 0.1;
// .maxPolarAngle : Float
// 你能够垂直旋转的角度的上限，范围是0到Math.PI，其默认值为Math.PI。
// .minPolarAngle : Float
// 你能够垂直旋转的角度的下限，范围是0到Math.PI，其默认值为0。
controls.maxPolarAngle = Math.PI / 4 * 2.5;
controls.minPolarAngle = Math.PI / 4 * 2.5;

// 设置时钟
// Clock该对象用于跟踪时间。如果performance.now可用，则 Clock 对象通过该方法实现，否则回落到使用略欠精准的Date.now来实现。
// .oldTime : Float 存储时钟最后一次调用 start, .getElapsedTime() 或 .getDelta() 方法的时间。默认值是 0。
const clock = new THREE.Clock();


function render() {
    let elapsedTime = clock.getElapsedTime();

    controls.update();
    renderer.render(scene, camera);
    // requestAnimationFrame 是一个用于优化浏览器动画效果的 API。它可以让浏览器在下一次重绘前执行指定的回调函数，
    // 从而可以更加流畅地执行动画效果，避免了使用 setTimeout 或 setInterval 可能引起的性能问题。
    requestAnimationFrame(render);
}

render();

// 窗口变化时，更新渲染画面
window.addEventListener("resize", () => {

    // 更新摄像机视锥体长宽比
    camera.aspect = window.innerWidth / window.innerHeight;
    // 更新摄像机投影矩阵。在任何参数被改变以后必须被调用
    camera.updateProjectionMatrix();

    // 更新渲染器
    renderer.setSize(window.innerWidth, window.innerHeight);
    // 设置渲染器像素比 通常用于避免HiDPI设备上绘图模糊
    renderer.setPixelRatio(window.devicePixelRatio);

})


